In multiple inheritance, a class
can be derived from more than one base classes
. The syntax for multiple inheritance is similar to single inheritance except we list all the base classes in sequence instead of one base class in single inheritance.
class ChildClass(<base_class_1>, <base_class_2>, ...):
.... Child Class code
Lets create few classes and expore the features of Multiple Inheritance.
In [24]:
class Base1:
pass
class Base2:
pass
class MultiDerived(Base1, Base2):
pass
Lets create the class MultiDerived
child class using Base1
& Base2
.
In [25]:
md = MultiDerived()
print(md)
The above object is a blank object with contains no custom defined attributes. Lets explore it in more details.
In [26]:
class P1():
def __init__(self):
print("P-1 init")
class P2():
def __init__(self):
print("P_2 init")
class C1(P1, P2):
pass
class C2(P2, P1):
pass
c1 = C1()
print("*"*20)
c2 = C2()
In the above example, we have created two child classes C1
and C2
with P1
, P2
and P2
, P1
as parents respectively. You will note that when we created child c1
of class C1
, as C1
do not have any __init__
function, Python searched it on parent classes, and executed & returned after finding its first definition, which it found in P1
class, thus it did not ran the __init__
of P2
. Similarly c2
ran the __init__
of P2
class. We will explain the algo which is used to find the required functions on parents later in the chapter.
Lets create a child class TechLead
which is derived from Lead
and Tech
classes.
In [2]:
class Tech(object):
def __init__(self, tech):
self._tech_name = tech
@property
def tech_name(self):
return self._tech_name
@tech_name.setter
def tech_name(self, name):
self._tech_name = name
class Lead(object):
def __init__(self, reportee_count):
self._reportee_count = reportee_count
@property
def reportee(self):
return self._reportee_count
@reportee.setter
def reportee(self, count):
self._reportee_count = count
class TechLead(Tech, Lead):
def __init__(self, tech, count):
Lead.__init__(self, count)
Tech.__init__(self, tech)
vivek = TechLead("Java", 2)
print(vivek)
print(vivek.__dict__)
Lets try the same using super function
In [5]:
class Tech(object):
def __init__(self, tech, **kwds):
print("Inside Tech")
self._tech_name = tech
super().__init__(**kwds)
@property
def tech_name(self):
return self._tech_name
@tech_name.setter
def tech_name(self, name):
self._tech_name = name
class Lead(object):
def __init__(self, count_reportee, **kwds):
print("Inside Lead")
self._reportee_count = count_reportee
super().__init__(**kwds)
@property
def reportee(self):
return self._reportee_count
@reportee.setter
def reportee(self, count):
self._reportee_count = count
class TechLead(Tech, Lead):
def __init__(self, name, **kwds):
print("Inside TechLead")
self._name = name
super().__init__(**kwds)
abhi = TechLead(name="Abhishek Kumar", tech="macOS", count_reportee= 10)
print(abhi)
print(abhi.__dict__)
In [9]:
class Tech(object):
def __init__(self, tech, **kwds):
print("Inside Tech")
self._tech_name = tech
# super().__init__(**kwds)
@property
def tech_name(self):
return self._tech_name
@tech_name.setter
def tech_name(self, name):
self._tech_name = name
class Lead(object):
def __init__(self, count_reportee, **kwds):
print("Inside Lead")
self._reportee_count = count_reportee
super().__init__(**kwds)
@property
def reportee(self):
return self._reportee_count
@reportee.setter
def reportee(self, count):
self._reportee_count = count
class TechLead(Tech, Lead
):
def __init__(self, name, **kwds):
print("Inside TechLead")
self._name = name
super().__init__(**kwds)
abhi = TechLead(name="Abhishek Kumar", count_reportee= 10, tech="macOS")
print(abhi)
print(abhi.__dict__)
Few things of importance you will find are as follows
By adding super().__init__(**kwds)
in all the parents __init__
functions, and adding super().__init__(**kwds)
in child __init__
function, we can make sure that init of every parent is called automatically.
We can use similar to def __init__(self, count_reportee, **kwds)
as __init__
function signature, where count_reportee
is the needed parameter for that class.
In [3]:
class Base1:
def test(self):
print("in Base1 -> test")
class Base2:
def test(self):
print("in Base2 -> test")
class MultiDerived(Base1, Base2):
def test2(self):
super().test()
Base2.test(Base2)
class MultiDerived2(Base2, Base1):
def test2(self):
super().test()
Base2.test(Base2)
print("Please check the result of test()")
md = MultiDerived()
md.test2()
md.test()
print("*"*10)
md2 = MultiDerived2()
md2.test2()
md2.test()
In [ ]:
Note in the above example, when we created c1
an object of C1
and as C1
class do not have initializing function __init__
thus Python searched its parent
In [ ]:
class Base:
pass
class Derived1(Base):
pass
class Derived2(Derived1):
pass
In the multiple inheritance scenario, any specified attribute is searched first in the current class. If not found, the search continues into parent classes in depth-first, left-right fashion without searching same class twice
In [32]:
class Base:
def test(self):
print("In Base test")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
def test_alone(self):
print("In Base test: test_alone")
class Derived2(Derived1):
def test2(self):
print("in Derived2 test2")
obj = Derived2()
obj.test()
obj.test2()
obj.test_alone()
Base.test(Base)
In [9]:
class Base:
def test(self):
print("In Base test")
def test_base(self):
print("test_base")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
self.test_base()
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
self.test()
obj = Derived1()
obj.test()
obj.test_alone("test")
Base.test(Base)
In [11]:
class Base:
def test(self):
print("In Base test")
def test_base(self):
print("test_base")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
Base.test_base(Base)
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
self.test()
obj = Derived1()
obj.test()
obj.test_alone("test")
Base.test(Base)
In [6]:
class Base:
def test(self):
print("In Base test")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
def test_alone(self):
print("In Base test: test_alone")
class Derived2(Derived1):
def test2(self):
print("in Derived2 test2")
obj = Derived2()
obj.test()
obj.test2()
obj.test_alone()
Base.test(Base)
In [3]:
class Base():
def test1(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
class Derived3(Derived1):
pass
d = Derived3()
d.test()
d.test1()
In [27]:
class Base():
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test", end=", ")
return "Golu"
class Derived3(Derived1):
pass
d = Derived3()
print(d.test())
In [39]:
#### Explicitly calling function
class Base:
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
class Derived2(Derived1):
pass
obj = Derived2
obj.test(obj)
Derived2.test(Derived2)
In [41]:
#### Explicitly calling function
class Base:
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
print(type(self))
class Derived2(Derived1):
pass
obj = Derived2
obj.test(obj)
Derived2.test(Derived2)
In [61]:
#### Explicitly calling function
class Base:
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
print(type(self))
class Derived2(Derived1):
pass
obj = Derived2()
obj.test()
Derived2.test(Derived2)
In [2]:
## TODO : Need to find a way to call init of both the parents
In [ ]:
In [3]:
class CL1(object):
def __init__(self):
# super(CL1, self).__init__()
print ("class 1")
class CL2(object):
def __init__(self):
# super(CL2, self).__init__()
print ("class 2")
class CL3(CL1, CL2):
def __init__(self):
super(CL3, self).__init__()
print ("class 3")
instance = CL3()
CMR is explained by Guido at http://python-history.blogspot.com/2010/06/method-resolution-order.html.
We will try to summerize it here.
In [15]:
class A:
def whereiam(self):
print("I am in A")
class B:
def whoiam(self):
print("I am a method")
class C(A, B):
pass
In [19]:
c = C()
print(dir(c))
c.whoiam()
In [21]:
class A:
def whoiam(self):
print("I am in A")
class B:
def whoiam(self):
print("I am a method")
class C(A, B):
pass
In [22]:
c = C()
print(dir(c))
c.whoiam()
In [26]:
class A:
def whereiam(self):
print("I am in A")
class B(A):
def whereiam(self):
print("I am in B")
class C(A):
def whereiam(self):
print("I am in C")
class D(B, C):
def whereiam(self):
print("I am in D")
In [27]:
d = D()
d.whereiam()
In [28]:
class A:
def whereiam(self):
print("I am in A")
class B(A):
def whereiam(self):
print("I am in B")
class C(A):
def whereiam(self):
print("I am in C")
class D(B, C):
pass
In [29]:
d = D()
d.whereiam()
In [30]:
class A:
def whereiam(self):
print("I am in A")
class B(A):
pass
class C(A):
def whereiam(self):
print("I am in C")
class D(B, C):
pass
In [31]:
d = D()
d.whereiam()
In [47]:
class A:
def whereiam(self):
print("I am in A")
class D:
def whereiam(self):
print("I am in D")
class B(A):
pass
class C(D):
pass
class E(B, C):
pass
In [48]:
e = E()
e.whereiam()
In [ ]:
C3 super-class linearization follow the following rules:
In [35]:
class Type(type):
def __repr__(cls):
return cls.__name__
class O(object, metaclass=Type): pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(O): pass
class E(O): pass
class K1(A, B, C): pass
class K2(D, B, E): pass
class K3(D, A): pass
class Z(K1, K2, K3): pass
In [36]:
Z.mro()
Out[36]:
Problem
class First(object):
def __init__(self):
print("first")
class Second(First):
def __init__(self):
print("second")
class Third(First, Second):
def __init__(self):
print("third")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-37-a1afb9897f3d> in <module>()
7 print("second")
8
----> 9 class Third(First, Second):
10 def __init__(self):
11 print("third")
TypeError: Cannot create a consistent method resolution
order (MRO) for bases First, Second
In [ ]:
In [2]:
class First(object):
def __init__(self):
print("first")
class Second(First):
def __init__(self):
print("second")
class Third(Second):
def __init__(self):
print("third")
In [4]:
Third.mro()
Out[4]:
In [ ]:
In [5]:
class O(object): pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(O): pass
class E(O): pass
class K1(A, B, C): pass
class K2(D, B, E): pass
class K3(D, A): pass
class Z(K1, K2, K3): pass
In [1]:
print(Z.mro())
In [3]:
class O(object): pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(O): pass
class E(O): pass
class K1(A, B, C): pass
class K2(D, B, E): pass
In [6]:
O.__subclasses__()
Out[6]:
thus, we can create the following code the get the sub-classes
name
In [14]:
print([cls.__name__ for cls in O.__subclasses__()])
Lets create a small function based on the above code sample
In [13]:
def get_subclasses(cls):
lst = []
for a in cls.__subclasses__():
lst.append(a)
lst.extend(get_subclasses(a))
return set(lst)
get_subclasses(O)
Out[13]:
In [ ]:
In [ ]:
In [ ]: